package day01;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Set;

/**
 * @author chenshijie
 * @date 2023/12/14 19:20
 */

public class NIOServer {
    public static void main(String[] args) throws IOException {
        // 创建两个Selector对象，其主要的作用便是通过select阻塞，来获取感兴趣的对象并处理该对象。
        Selector serverSelector = Selector.open();
        Selector clientSelector = Selector.open();
        new Thread(() -> {
            try {
                // 对应IO编程中的服务端启动
                // 这里分为几个步骤，
                // 1. 创建ServerSocketChannel类，其作用在于注册器，注册在Selector上
                // 2. 在accept事件中，channel要绑定监听端口
                // 3. 将channel设置为非阻塞
                // 4. 将channel注册到ServerSelector要轮询的事件中，同时在这里可以选定感兴趣的是什么事件，有OP_ACCEPT,READ,WRITE,CONNECT
                ServerSocketChannel listenerChannel = ServerSocketChannel.open();
                listenerChannel.socket().bind(new InetSocketAddress(8001));
                listenerChannel.configureBlocking(false);
                listenerChannel.register(serverSelector, SelectionKey.OP_ACCEPT);
                while (true) {
                    // 监测是否有新连接，这里的1指阻塞的时间为1ms
                    if (serverSelector.select(1) > 0) {
                        // 当有事件触发的时候，我们可以通过selectedKeys()或得到set集合
                        Set<SelectionKey> set = serverSelector.selectedKeys();
                        Iterator<SelectionKey> keyIterator = set.iterator();
                        while (keyIterator.hasNext()) {
                            SelectionKey key = keyIterator.next();
                            if (key.isAcceptable()) {
                                try {
                                    // （1）每来一个新的连接，不需要创建一个线程，而是直接注册到clientSelector
                                    ServerSocketChannel channel = (ServerSocketChannel)key.channel();
                                    SocketChannel clientChannel = channel.accept();
                                    clientChannel.configureBlocking(false);
                                    clientChannel.register(clientSelector, SelectionKey.OP_READ);
                                } finally {
                                    // 已经连接上了就要及时将其remove();
                                    keyIterator.remove();
                                }
                            }
                        }
                    }
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }).start();

        new Thread(() -> {
            try {
                while (true) {
                    // (2) 批量轮询哪些连接有数据可读，这里的1指阻塞的事件为1ms
                    if (clientSelector.select(1) > 0) {
                        Set<SelectionKey> set = clientSelector.selectedKeys();
                        Iterator<SelectionKey> keyIterator = set.iterator();
                        while (keyIterator.hasNext()) {
                            SelectionKey key = keyIterator.next();
                            if (key.isReadable()) {
                                try {
                                    SocketChannel clientChannel = (SocketChannel)key.channel();
                                    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                                    // (3) 面向buffer
                                    clientChannel.read(byteBuffer);
                                    byteBuffer.flip();
                                    System.out.println(Charset.defaultCharset().newDecoder().decode(byteBuffer).toString());
                                } finally {
                                    keyIterator.remove();
                                    // 读取是一次性的，因此要再把事件注册回去。
                                    key.interestOps(SelectionKey.OP_READ);
                                }
                            }
                        }
                    }

                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }).start();
    }
}
